/**
 * 
 */
package tags;

import groovy.lang.Closure;
import groovyjarjarcommonscli.MissingArgumentException;

import java.io.PrintWriter;
import java.util.List;
import java.util.Map;

import play.libs.Codec;
import play.modules.extendedValidation.singleton.HashMapSingleton;
import play.templates.BaseTemplate;
import play.templates.FastTags;
import play.templates.GroovyTemplate.ExecutableTemplate;
import play.templates.JavaExtensions;

/**
 * @author Ahmed
 * 
 */
@FastTags.Namespace("jqv")
public final class VTags extends FastTags {

	/**
	 * Puts the scripts and styles for validating this form in
	 * {@code moreScripts} & {@code moreStyles} properties and generates html
	 * form element as specified by this tag
	 * {@link FastTags#_form(Map, Closure, PrintWriter, ExecutableTemplate, int)}
	 * 
	 * @param args
	 *            tag attributes tag attributes
	 * 
	 * @param body
	 *            tag inner body
	 * @param out
	 *            the output writer
	 * @param template
	 *            enclosing template
	 * @param fromLine
	 *            template line number where the tag is defined
	 * 
	 */
	public static void _vform(Map<?, ?> args, Closure body, PrintWriter out,
			ExecutableTemplate template, int fromLine) {
		if (args.containsKey("class")) {
			((Map<Object, Object>) args).put("class", args.get("class")
					+ " TBV");
		} else {
			((Map<Object, Object>) args).put("class", "TBV");
		}
		if (args.containsKey("edit")) {
			BaseTemplate.layoutData.get().put("_editObject_", args.get("edit"));
		}
		_form(args, body, out, template, fromLine);

	}

	/**
	 * Generates a html input element of type password linked to a field in
	 * model and validated accordingly.
	 * 
	 * @param args
	 *            tag attributes
	 * 
	 * @param body
	 *            tag inner body
	 * @param out
	 *            the output writer
	 * @param template
	 *            enclosing template
	 * @param fromLine
	 *            template line number where the tag is defined
	 * @throws Exception
	 * 
	 */
	public static void _pass(Map<?, ?> args, Closure body, PrintWriter out,
			ExecutableTemplate template, int fromLine) throws Exception {
		String passInput = "<input type=\"password\" class=\"%s %s\" id=\"%s\" name=\"%s\" %s />";
		String[] modelField = getModelField(args);
		String validation = formatValidation(modelField, args, "");
		passInput = formatHtmlElementAttributes(args, passInput, modelField,
				validation);
		out.print(passInput);
	}

	/**
	 * Generates a html input element of type text linked to a field whose type
	 * is Date or Calendar in model and validated accordingly. It supports time,
	 * Date, & DateTime inputs.
	 * 
	 * @param args
	 *            tag attributes
	 * 
	 * @param body
	 *            tag inner body
	 * @param out
	 *            the output writer
	 * @param template
	 *            enclosing template
	 * @param fromLine
	 *            template line number where the tag is defined
	 * @throws Exception
	 * 
	 */
	public static void _date(Map<?, ?> args, Closure body, PrintWriter out,
			ExecutableTemplate template, int fromLine) throws Exception {
		// TODO Datepicker will be added on request.

		String dateInput = "<input type=\"text\" class=\"%s %s datepicker\" id=\"%s\" name=\"%s\" %s />";
		String[] modelField = getModelField(args);
		String dateValidation = (String) args.get("type"); // date, dateTime,
															// Time.
		if (dateValidation == null) {
			dateValidation = ",match[dateFormat]";
		} else {
			dateValidation = "match[" + dateValidation + "Format]";
		}
		String validation = formatValidation(modelField, args, dateValidation);
		dateInput = formatHtmlElementAttributes(args, dateInput, modelField,
				validation);
		out.print(dateInput);
	}

	/**
	 * Generates a html input element of type text linked to a field in model
	 * and validated accordingly.
	 * 
	 * @param args
	 *            tag attributes
	 * 
	 * @param body
	 *            tag inner body
	 * @param out
	 *            the output writer
	 * @param template
	 *            enclosing template
	 * @param fromLine
	 *            template line number where the tag is defined
	 * @throws Exception
	 * 
	 */
	public static void _text(Map<?, ?> args, Closure body, PrintWriter out,
			ExecutableTemplate template, int fromLine) throws Exception {
		String textInput = "<input type=\"text\" class=\"%s %s\" id=\"%s\" name=\"%s\" %s />";
		String[] modelField = getModelField(args);
		String validation = formatValidation(modelField, args, "");
		textInput = formatHtmlElementAttributes(args, textInput, modelField,
				validation);
		out.print(textInput);
	}

	/**
	 * Generates a html input element of type textarea linked to a field in
	 * model and validated accordingly.
	 * 
	 * @param args
	 *            tag attributes
	 * 
	 * @param body
	 *            tag inner body
	 * @param out
	 *            the output writer
	 * @param template
	 *            enclosing template
	 * @param fromLine
	 *            template line number where the tag is defined
	 * @throws Exception
	 * 
	 */
	public static void _textarea(Map<?, ?> args, Closure body, PrintWriter out,
			ExecutableTemplate template, int fromLine) throws Exception {
		String textInput = "<textarea class=\"%s %s\" id=\"%s\" name=\"%s\" %s>";
		String[] modelField = getModelField(args);
		String validation = formatValidation(modelField, args, "");
		textInput = formatHtmlElementAttributes(args, textInput, modelField,
				validation);
		out.print(textInput);
		out.print((body == null ? "" : JavaExtensions.toString(body)));
		out.print(setDefaultValue(modelField).toString());
		out.print("</textarea>");
	}

	/**
	 * Generates a html input element of type select linked to a field in model
	 * and validated accordingly.
	 * 
	 * @param args
	 *            tag attributes
	 * 
	 * @param body
	 *            tag inner body
	 * @param out
	 *            the output writer
	 * @param template
	 *            enclosing template
	 * @param fromLine
	 *            template line number where the tag is defined
	 * 
	 */
	public static void _select(Map<?, ?> args, Closure body, PrintWriter out,
			ExecutableTemplate template, int fromLine) {
		// TODO implement the select Element.
	}

	/**
	 * Generates a html input element of type file and supports extra validation
	 * like checking for extensions, min size, or max size.
	 * 
	 * @param args
	 *            tag attributes
	 * 
	 * @param body
	 *            tag inner body
	 * @param out
	 *            the output writer
	 * @param template
	 *            enclosing template
	 * @param fromLine
	 *            template line number where the tag is defined
	 * @throws Exception
	 * 
	 */
	public static void _file(Map<?, ?> args, Closure body, PrintWriter out,
			ExecutableTemplate template, int fromLine) throws Exception {
		String fileInput = "<input type=\"file\" class=\"%s %s\" id=\"%s\" name=\"%s\" %s />";
		Object acceptExtentions = args.get("accept");// match.
		Object rejectExtentions = args.get("reject");// match.
		Object minSize = args.get("min");// minSize.
		Object maxSize = args.get("max");// maxsize.
		String[] modelField = getModelField(args);

		StringBuffer extraValidation = new StringBuffer();
		if (acceptExtentions != null) {
			extraValidation.append(",endsWith[.*?");

			extraValidation.append(acceptExtentions.toString().replace(",",
					"$|"));
			extraValidation.append("$]");
		}
		if (rejectExtentions != null) {
			extraValidation.append(",notEndsWith[.*?");

			extraValidation.append(rejectExtentions.toString().replace(",",
					"$|"));
			extraValidation.append("$]");
		}
		if (minSize != null) {
			extraValidation.append(",minSize[");
			extraValidation.append(minSize);
			extraValidation.append("]");
		}
		if (maxSize != null) {
			extraValidation.append(",maxSize[");
			extraValidation.append(maxSize);
			extraValidation.append("]");
		}

		String validation = formatValidation(modelField, args,
				extraValidation.toString());
		fileInput = formatHtmlElementAttributes(args, fileInput, modelField,
				validation);
		out.print(fileInput);
	}

	/**
	 * Generates a html input element of type checkBox linked to a field in
	 * model or not and validated accordingly. For more info about the generated
	 * validation {@link VTags#getCheckBoxValidation(Map, boolean)}.
	 * 
	 * @param args
	 *            tag attributes
	 * 
	 * @param body
	 *            tag inner body
	 * @param out
	 *            the output writer
	 * @param template
	 *            enclosing template
	 * @param fromLine
	 *            template line number where the tag is defined
	 * @throws Exception
	 * 
	 */
	public static void _checkbox(Map<?, ?> args, Closure body, PrintWriter out,
			ExecutableTemplate template, int fromLine) throws Exception {
		String htmlElement = "<input type=\"checkbox\" class=\"%s %s\" id=\"%s\" name=\"%s\" />";
		String[] modelField = getModelField(args);
		String validation = getCheckBoxValidation(args, true);
		if (validation == null || validation.isEmpty()) {
			validation = "";
		} else {
			validation = "validate[" + validation + "]";
		}
		htmlElement = formatHtmlElementAttributes(args, htmlElement,
				modelField, validation);
		// title=\"%s\" value=\"%s\"
		Object title = args.get("title");
		Object value = args.get("value");
		Object checked = args.get("checked");
		if (checked == null) {
			checked = "";
		} else {
			checked = "checked=\"true\"";
		}

		if (title == null) {
			title = "";
		} else {
			title = "title=\"" + title + "\"";
		}

		if (value == null) {
			value = "";
		} else {
			value = "value=\"" + value + "\"";
		}
		out.print(htmlElement.replace("/>", title + " " + value + checked
				+ "/>"));
	}

	/**
	 * Generates a html input element of type Radio Button linked to a field in
	 * model and validated accordingly.
	 * 
	 * @param args
	 *            tag attributes
	 * 
	 * @param body
	 *            tag inner body
	 * @param out
	 *            the output writer
	 * @param template
	 *            enclosing template
	 * @param fromLine
	 *            template line number where the tag is defined
	 * 
	 */
	public static void _radio(Map<?, ?> args, Closure body, PrintWriter out,
			ExecutableTemplate template, int fromLine) {
		// TODO implement radio button element.
	}

	// TODO document _checkBoxList
	/**
	 * @param args
	 *            tag attributes
	 * 
	 * @param body
	 *            tag inner body
	 * @param out
	 *            the output writer
	 * @param template
	 *            enclosing template
	 * @param fromLine
	 *            template line number where the tag is defined
	 * @throws NoSuchFieldException
	 * @throws IllegalAccessException
	 * @throws SecurityException
	 * @throws IllegalArgumentException
	 * 
	 */
	public static void _checkboxList(Map<?, ?> args, Closure body,
			PrintWriter out, ExecutableTemplate template, int fromLine)
			throws Exception {
		String htmlElement = "<input type=\"checkbox\" class=\"%s %s\" id=\"%s\" name=\"%s\" title=\"%s\" value=\"%s\"/>";
		String value = (String) args.get("value");
		String title = (String) args.get("title");
		List<?> items = (List<?>) args.get("items");
		if (items.size() == 0) {
			return;
		}
		String checkboxListValidation = getCheckBoxValidation(args, false);

		printTheList(items, items.get(0).getClass(), title, value, htmlElement,
				out, args, checkboxListValidation);

	}

	/**
	 * Extracts validation info from the tag attributes {@code arg} that are
	 * valid for inputs of type check box.
	 * 
	 * @param args
	 *            tag attributes
	 * @param isSingle
	 *            to check if the validation for single checkbox or for a group
	 *            of checkboxes.
	 * @return String representing the validation extracted.
	 */
	private static String getCheckBoxValidation(Map<?, ?> args, boolean isSingle) {
		Object minCheckbox = args.get("min");
		Object maxCheckbox = args.get("max");
		if (minCheckbox == null) {
			minCheckbox = "";
		} else {
			minCheckbox = "minCheckbox[" + minCheckbox + "]";
		}

		if (maxCheckbox == null) {
			maxCheckbox = "";
		} else {
			maxCheckbox = ",maxCheckbox[" + maxCheckbox + "]";
		}
		if (isSingle) {
			boolean isRequired = args.get("required") != null;
			Object group = args.get(",group");
			if (group == null) {
				group = "";
			} else {
				group = "groupRequired[" + group + "]";
			}
			minCheckbox = minCheckbox.toString() + group
					+ (isRequired ? "required" : "");
		}
		return minCheckbox + maxCheckbox.toString();
	}

	// TODO document _radioList
	/**
	 * 
	 * @param args
	 *            tag attributes
	 * 
	 * @param body
	 *            tag inner body
	 * @param out
	 *            the output writer
	 * @param template
	 *            enclosing template
	 * @param fromLine
	 *            template line number where the tag is defined
	 * @throws Exception
	 * 
	 */
	public static void _radioList(Map<?, ?> args, Closure body,
			PrintWriter out, ExecutableTemplate template, int fromLine)
			throws Exception {
		String radioButton = "<input type=\"radio\" class=\"%s %s\" id=\"%s\" name=\"%s\" title=\"%s\" value=\"%s\"/>";
		String value = (String) args.get("value");
		String title = (String) args.get("title");
		List<?> items = (List<?>) args.get("items");
		String validation = (args.get("required") == null ? "" : "required");

		if (items.size() == 0) {
			return;
		}
		printTheList(items, items.get(0).getClass(), title, value, radioButton,
				out, args, validation);
	}

	// TODO document _confirm
	/**
	 * @param args
	 *            tag attributes
	 * 
	 * @param body
	 *            tag inner body
	 * @param out
	 *            the output writer
	 * @param template
	 *            enclosing template
	 * @param fromLine
	 *            template line number where the tag is defined
	 * @throws MissingArgumentException
	 *             will be thrown by this method
	 *             {@link VTags#getModelField(Map)}
	 * 
	 */
	public static void _confirm(Map<?, ?> args, Closure body, PrintWriter out,
			ExecutableTemplate template, int fromLine) {
		// TODO implement confirm element.
	}

	/**
	 * Puts links for required javascript files and stylesheet files for JQuery
	 * Validation for this form only if these files are not in the script and
	 * style properties {@link VTags.addScriptToHead} and
	 * {@link VTags.addStylesToHead}.
	 * 
	 * @param formID
	 *            the formID which will be validated will be used to attach the
	 *            validation engine to the form.
	 */
	/**
	 * private static void addScriptAndStyleForValidation(Object formID) {
	 * 
	 * /* String extraScripts =
	 * "<script type=\"text/javascript\" language=\"javascript\"  " +
	 * "src=\"/public/javascripts/jquery.validationEngine-en.js\"></script>\n<script type=\"text/javascript\" "
	 * +
	 * "language=\"javascript\"  src=\"/public/javascripts/jquery.validationEngine.js\"></script>\n"
	 * ;
	 */
	/**
	 * String formQuery = "<script>\n" +
	 * "jQuery(document).ready(function(){\n// binds form submission and fields to the validation engine\n"
	 * + "jQuery(\".%s\")." + "validationEngine();\n});</script>\n"; formQuery =
	 * String.format(formQuery, formID);
	 */
	/*
	 * String extraStyles = "<link rel=\"stylesheet\" type=\"text/css\" " +
	 * "href=\"/public/stylesheets/validationEngine.jquery.css\" charset=\"utf-8\">\n<link "
	 * +
	 * "rel=\"stylesheet\" type=\"text/css\" href=\"/public/stylesheets/template.css\" charset=\"utf-8\">"
	 * ;
	 */

	/*
	 * addScriptToHead(extraScripts, "jquery.validationEngine.js");
	 * addStylesToHead(extraStyles, "validationEngine.jquery.css");
	 */
	/**
	 * addScriptToHead(formQuery, String.format("jQuery(\".%s\")", formID)); }
	 */
	/**
	 * Puts the {@code extraScripts} in {@code moreScripts}property which is
	 * supposed to be in the <head></head> tag.
	 * 
	 * @param extraScripts
	 *            extra javascripts to be put in the property
	 *            {@code moreScripts} which is supposed to be in the Head Tag.
	 * @param strToFind
	 *            is a string if found in the {@code moreScripts} property then
	 *            the {@code extraScripts} won't be added.
	 */
	/**
	 * private static void addScriptToHead(String extraScripts, String
	 * strToFind) { String previousScripts = (String)
	 * BaseTemplate.layoutData.get().get( "moreScripts"); if (previousScripts ==
	 * null) { previousScripts = ""; }
	 * 
	 * if (extraScripts != null && !previousScripts.contains(strToFind)) {
	 * BaseTemplate.layoutData.get().put("moreScripts", previousScripts + "\n" +
	 * extraScripts); }
	 * 
	 * }
	 */
	/**
	 * Puts the {@code extraStyles} in {@code moreStyles} property which is
	 * supposed to be in the {@code <head></head>} tag.
	 * 
	 * @param extraStyles
	 *            extra style sheets to be put in the property
	 *            {@code moreStyles} which is supposed to be in the Head Tag.
	 * @param strToFind
	 *            is a string if found in the {@code moreStyles} property then
	 *            the {@code extraStyles} won't be added.
	 */
	/**
	 * private static void addStylesToHead(String extraStyles, String strToFind)
	 * { String previousStyles = (String) BaseTemplate.layoutData.get().get(
	 * "moreStyles"); if (previousStyles == null) { previousStyles = ""; } if
	 * (extraStyles != null && !previousStyles.contains(strToFind)) {
	 * BaseTemplate.layoutData.get().put("moreStyles", previousStyles + "\n" +
	 * extraStyles); } }
	 */

	/**
	 * Formats the given Model.Field validation. It appends ajax, groupRequired
	 * and Custom Function validations to the auto generated validation at
	 * startup.
	 * 
	 * @param modelField
	 *            String[] consists of 2 elements which are model name & field
	 *            name respectively.
	 * @param args
	 *            the calling tag attributes
	 * @param extraValidation
	 *            extra validation provided by the calling tag.
	 * @return String representing the validation for this field to be added by
	 *         the calling tag.
	 */
	private static String formatValidation(String[] modelField, Map<?, ?> args,
			String extraValidation) {
		// TODO add support for custom and ajax validation.
		Object group = args.get("group");
		Object customFunction = args.get("custom");
		Object ajaxRule = args.get("ajax");
		String autoformatdValidation = null;
		try {
			autoformatdValidation = HashMapSingleton.getClassFieldValidation()
					.get(modelField[0]).get(modelField[1]);
		} catch (Exception e) {
		}

		if (ajaxRule == null) {
			ajaxRule = "";
		} else {
			ajaxRule = ",ajax[" + ajaxRule + "]";
		}

		if (customFunction == null) {
			customFunction = "";
		} else {
			customFunction = ",funcCall[" + customFunction + "]";
		}

		if (autoformatdValidation == null) {
			autoformatdValidation = "";
		}

		if (group == null) {
			group = "";

		} else {
			group = ",groupRequired[" + group + "]";
			autoformatdValidation.replace(",required", "");
		}
		// validate[autovalidation,groupRequired[],dateValidation,customfun,ajax]
		String validation = String.format("validate[%s%s%s%s%s]",
				autoformatdValidation, group, extraValidation, customFunction,
				ajaxRule);
		if (validation.length() == 10) {
			validation = "";
		}
		return validation;

	}

	/**
	 * Generates a formatted HTML element linked to a certain model field given
	 * a string representation for this element.
	 * 
	 * @param args
	 *            the calling tag attributes
	 * @param htmlElement
	 *            String representation for this element contains placeHolders
	 *            (%s) to embedd in it its attributes.
	 * @param modelField
	 *            the return of this method {@link VTags#getModelField(Map)}
	 * @return String representing the HTML element after embedding the
	 *         attributes and the validation in it.
	 * @throws Exception
	 */
	private static String formatHtmlElementAttributes(Map<?, ?> args,
			String htmlElement, String[] modelField, String validation)
			throws Exception {
		Object id = args.get("id");
		Object _class = args.get("class");
		Object name = args.get("name");
		Object value = setDefaultValue(modelField);
		if(value == null){
			value ="";
		} else {
			value = "value=\"" + value  + "\"";
		}
		if (name == null && id == null) {
			name = modelField[1];
			id = modelField[0] + name;
		} else if (id == null) {
			id = name;
		} else if (name == null) {
			name = id;
		}

		if (_class == null) {
			_class = modelField[0];
		}

		return String.format(htmlElement, validation, _class, id, name, value);

	}

	private static Object setDefaultValue(String[] modelField) throws Exception {
		if (BaseTemplate.layoutData.get().containsKey("_editObject_")) {
			Object obj = BaseTemplate.layoutData.get().get("_editObject_");
			return getFieldValue(obj, modelField[1]);
		}
		return "";

	}

	/**
	 * gets the model name & the field name from a map of arguments by the key
	 * "field" and puts them in an array.
	 * 
	 * @param args
	 *            tag attributes
	 * @return String[] representing the Model in index 0 and the Field in index
	 *         1.
	 * @throws IllegalArgumentException
	 *             if the field argument doesn't contain the model name or the
	 *             field name
	 * 
	 */
	private static String[] getModelField(Map<?, ?> args) {
		Object field = args.get("for");
		if (field == null) {
			return new String[] { Codec.UUID().replace("-", ""),
					Codec.UUID().replace("-", "") };
		}// endif

		String[] modelField = field.toString().split("\\.");

		if (modelField.length != 2) {
			throw new IllegalArgumentException(
					"'field' argument should be in this form 'Model.field'");
		}// endif
		return modelField;
	}

	// TODO document printtheList method
	/**
	 * 
	 * @param items
	 * @param objClass
	 * @param title
	 * @param value
	 * @param htmlElement
	 * @param out
	 * @param args
	 *            tag attributes
	 * @param validation
	 * @throws Exception
	 */
	public static void printTheList(List<?> items, Class<?> objClass,
			String title, String value, String htmlElement, PrintWriter out,
			Map<?, ?> args, Object validation) throws Exception {
		String label = "<label class=\"%sLbl\" for=\"%s\">%s</label>";
		Object id = args.get("id");
		Object _class = args.get("class");
		Object name = args.get("name");
		boolean horizontal = args.get("horizontal") != null;
		String separator = "";
		if (horizontal) {
			separator = "&nbsp;&nbsp;&nbsp;&nbsp;";
		} else {
			separator = "<br/>";
		}
		if (id == null) {
			id = title;
		}
		if (name == null) {
			name = objClass.getSimpleName();
		}
		if (_class == null) {
			_class = objClass.getSimpleName();
		}

		int i = 1;
		for (Object obj : items) {
			String[] titleValue = new String[2];
			titleValue[0] = getFieldValue(obj, title).toString();
			titleValue[1] = getFieldValue(obj, value).toString();
			// class id title
			out.print(String.format(label, _class, id.toString() + i,
					titleValue[0]) + "&nbsp;");

			if (!validation.toString().isEmpty()) {
				validation = "validate[" + validation + "]";
			}
			// valid class id name title value
			out.println(String.format(htmlElement, validation, _class,
					id.toString() + i, name, titleValue[0], titleValue[1]));
			out.println(separator);
			i++;
		}
	}

	private static Object getFieldValue(Object obj, String fieldName)
			throws Exception {
		try {
			return obj.getClass().getField(fieldName).get(obj);
		} catch (Exception e) {
			return "";
		}
	}
}